// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-18

package src

import (
	"context"
	"os"
)

var singletonContainer *Container

// Container
// is a component used to manage all components for console.
type Container struct {
	argument       *Argument
	commands       map[string]*Command
	ctx            context.Context
	defaultCommand *Command
	output         *Output
}

// NewContainer
// returns a singleton instance of package.
func NewContainer() *Container {
	return singletonContainer
}

// Add
// a commands list into container.
func (o *Container) Add(commands ...*Command) {
	// Range commands in parameters.
	for _, command := range commands {
		// Ignore nil command.
		if command == nil {
			continue
		}

		// Ignore duplicated command.
		if _, ok := o.commands[command.name]; ok {
			continue
		}

		// Update command mapping.
		o.commands[command.name] = command

		// Set as default.
		if command.defaulter {
			o.defaultCommand = command
		}
	}
}

// Context
// returns a root context.
func (o *Container) Context() context.Context {
	return o.ctx
}

// Del
// deletes a commands list from container.
func (o *Container) Del(commands ...*Command) {
	for _, cmd := range commands {
		if _, ok := o.commands[cmd.name]; ok {
			o.DelByName(cmd.name)
		}
	}
}

// DelByName
// deletes a command from container.
func (o *Container) DelByName(name string) {
	if _, ok := o.commands[name]; ok {
		delete(o.commands, name)
	}
}

// GetArgument
// returns the singleton instance of Argument.
func (o *Container) GetArgument() *Argument {
	return o.argument
}

// GetCommand
// returns the singleton instance of Command if exists.
func (o *Container) GetCommand(name string) (cmd *Command, has bool) {
	cmd, has = o.commands[name]
	return
}

// GetCommands
// returns a commands mapper in container.
func (o *Container) GetCommands() map[string]*Command {
	return o.commands
}

// GetOutput
// returns the Output component.
func (o *Container) GetOutput() *Output {
	return o.output
}

// Run
// the container
func (o *Container) Run() {
	o.run(os.Args)
}

// RunWith
// run the container with given args.
func (o *Container) RunWith(args []string) {
	o.run(args)
}

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

func (o *Container) init() *Container {
	o.ctx = context.Background()
	o.argument = NewArgument()
	o.commands = make(map[string]*Command)
	o.output = NewOutput()
	return o
}

func (o *Container) run(args []string) {
	var (
		command = o.defaultCommand
		has     bool
	)

	// Parse
	// args on to fields.
	o.argument.Parse(args)

	// Use
	// command with given name by Argument.
	if name := o.argument.GetCommandName(); name != "" {
		if command, has = o.commands[name]; !has {
			o.output.Error(`not recognize command: %s`, name)
			return
		}
	}

	// Return
	// if default command not specified.
	if command == nil {
		o.output.Error(`command not specified`)
		return
	}

	// Verify
	// option and run it.
	if command.verify(o) {
		command.run(o.ctx, o)
	}
}
